home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 August: Tool Chest / Dev.CD Aug 00 TC Disk 1.toast / pc / sample code / files / mpfilecopy / mpfilecopy.c < prev    next >
Encoding:
Text File  |  2000-06-23  |  57.3 KB  |  1,779 lines

  1. /*
  2.     File:        MPFileCopy.c
  3.  
  4.     Contains:    An HFS Plus APIs copying engine, which optionally uses pre-emptive threads.
  5.  
  6.     Written by:    Quinn
  7.  
  8.     Copyright:    Copyright © 1999 by Apple Computer, Inc., All Rights Reserved.
  9.  
  10.                 You may incorporate this Apple sample source code into your program(s) without
  11.                 restriction. This Apple sample source code has been provided "AS IS" and the
  12.                 responsibility for its operation is yours. You are not permitted to redistribute
  13.                 this Apple sample source code as "Apple sample source code" after having made
  14.                 changes. If you're going to re-distribute the source, we require that you make
  15.                 it clear in the source that the code was descended from Apple sample source
  16.                 code, but that you've made changes.
  17.  
  18.     Change History (most recent first):
  19.  
  20. */
  21.  
  22. /////////////////////////////////////////////////////////////////
  23.  
  24. // MIB Setup
  25.  
  26. #include "MoreSetup.h"
  27.  
  28. // Mac OS Interfaces
  29.  
  30. #include <Files.h>
  31. #include <Multiprocessing.h>
  32. #include <Gestalt.h>
  33. #include <Events.h>
  34. #include <DriverGestalt.h>
  35. #include <Navigation.h>
  36. #include <UTCUtils.h>
  37.  
  38. // Standard C interfaces
  39.  
  40. #include <stdio.h>
  41. #include <stddef.h>
  42. #include <string.h>
  43.  
  44. // Wacky C interfaces (-:
  45.  
  46. #include <SIOUX.h>
  47.  
  48. // Project interfaces
  49.  
  50. #include "TradDriverLoaderLib.h"
  51. #include "MoreInterfaceLib.h"
  52.  
  53. /////////////////////////////////////////////////////////////////
  54. #pragma mark ----- Testing Flags -----
  55.  
  56. // Set kNeverUseMPTasks to true when debugging with a debugger
  57. // that doesn't handle debugging MP tasks.
  58.  
  59. enum {
  60.     kNeverUseMPTasks = false
  61. };
  62.  
  63. // kSpecialCaseClassicForks controls whether the classic
  64. // (resource and data) forks are copied by special case code
  65. // (true) or by the general purpose code (false).  This allows
  66. // you to test the general purpose code on current systems
  67. // which don't support named forks other than the classic ones.
  68.  
  69. enum {
  70.     kSpecialCaseClassicForks = true
  71. };
  72.  
  73. /////////////////////////////////////////////////////////////////
  74. #pragma mark ----- Tunable Parameters -----
  75.  
  76. // The following constants control the behaviour of the copy engine.
  77.  
  78. // BufferSizeForThisVolumeSpeed
  79.  
  80. enum {
  81. //    kDefaultCopyBufferSize =   2L * 1024 * 1024,            // Fast be not very responsive.
  82.     kDefaultCopyBufferSize = 256L * 1024,                    // Slower, but can still use machine.
  83.     kMaximumCopyBufferSize =   2L * 1024 * 1024,
  84.     kMinimumCopyBufferSize = 1024
  85. };
  86.  
  87. // CalculateForksToCopy
  88.  
  89. enum {
  90.     kExpectedForkCount     = 10                // Number of non-classic forks we expect.
  91. };
  92.  
  93. // CopyFolder
  94.  
  95. enum {
  96.     kMaximumFolderDepth     = 100,            // Arbitrary limit needed for stack allocation, see below.
  97.     kFolderItemsPerBulkCall = 20            // Trades File Manager efficiency versus memory usage, see below.
  98. };
  99.  
  100. /////////////////////////////////////////////////////////////////
  101. #pragma mark ----- Utilities -----
  102.  
  103. static OSStatus VolumeContainingFSRef(const FSRef *itemRef, FSVolumeRefNum *volume)
  104. {
  105.     OSStatus err;
  106.     FSCatalogInfo catInfo;
  107.     
  108.     err = FSGetCatalogInfo(itemRef, kFSCatInfoVolume, &catInfo, nil, nil, nil);
  109.     if (err == noErr) {
  110.         *volume = catInfo.volume;
  111.     }
  112.     return err;
  113. }
  114.  
  115. static OSStatus IsFileSharingRunning(Boolean *fileSharingIsRunning)
  116.     // Determines whether File Sharing is running.  It does this in
  117.     // the most paranoid way, by checking to see whether the process
  118.     // is running.  I might be able to just get away with checking
  119.     // whether the volume is being shared, but that would require
  120.     // determining exactly what's causing the problem.  I decided
  121.     // to just be paranoid and say "if the File Sharing process is 
  122.     // around, we act as if File Sharing is running".
  123. {
  124.     OSStatus err;
  125.     Boolean done;
  126.     ProcessInfoRec processInfo;
  127.     ProcessSerialNumber psn;
  128.     
  129.     MoreAssertQ(fileSharingIsRunning != nil);
  130.     *fileSharingIsRunning = false;
  131.     
  132.     // Iterate through the process list.
  133.     
  134.     psn.highLongOfPSN = 0;
  135.     psn.lowLongOfPSN = kNoProcess;
  136.     done = false;
  137.     do {
  138.         err = GetNextProcess(&psn);
  139.         if (err == noErr) {
  140.         
  141.             // Get information on this specific process.
  142.             // If the signature matches File Sharing, 
  143.             // set *fileSharingIsRunning and leave.
  144.             
  145.             MoreBlockZero(&processInfo, sizeof(processInfo));
  146.             processInfo.processInfoLength = sizeof(processInfo);
  147.             err = GetProcessInformation(&psn, &processInfo);
  148.             if (err == noErr && processInfo.processSignature == 'hhgg') {
  149.                 *fileSharingIsRunning = true;
  150.                 done = true;
  151.             }
  152.         } else if (err == procNotFound) {
  153.             done = true;
  154.             err = noErr;
  155.         }
  156.     } while (err == noErr & ! done);
  157.  
  158.     return err;
  159. }
  160.  
  161. /////////////////////////////////////////////////////////////////
  162. #pragma mark ----- Transfer Buffer Size -----
  163.  
  164. static OSStatus GetFileSystemInfo(FSVolumeRefNum vRefNum,
  165.                                     Boolean *async, 
  166.                                     Boolean *localVolume, 
  167.                                     UInt32  *volumeBytesPerSecond)
  168.     // Returns information about the volume specified by vRefNum.
  169.     // This information allows us to customise our copy engine
  170.     // appropriately.  Customisation includes the size of the
  171.     // copy buffer we allocate, whether we test for asynchronicity
  172.     // by calling the low-level driver, and so on.
  173. {
  174.     OSStatus err;
  175.     GetVolParmsInfoBuffer volParms;
  176.     HParamBlockRec hpb;
  177.     UInt32 bytesPerSecond;
  178.     
  179.     hpb.ioParam.ioNamePtr = nil;
  180.     hpb.ioParam.ioVRefNum = vRefNum;
  181.     hpb.ioParam.ioBuffer = (Ptr) &volParms;
  182.     hpb.ioParam.ioReqCount = sizeof(volParms);
  183.  
  184.     err = PBHGetVolParmsSync(&hpb);
  185.     if (err == noErr) {
  186.         
  187.         // Version 1 of the GetVolParmsInfoBuffer included the vMAttrib
  188.         // field, so we don't really need to test ioActCount.  A noErr
  189.         // result indicates that we have the info we need.  This is
  190.         // just a paranoia check.
  191.         
  192.         MoreAssertQ(hpb.ioParam.ioActCount >= offsetof(GetVolParmsInfoBuffer, vMVolumeGrade));
  193.         
  194.         if (async != nil) {
  195.             *async = ((volParms.vMAttrib & (1 << bSupportsAsyncRequests)) != 0);
  196.         }
  197.         if (localVolume != nil) {
  198.             *localVolume = (volParms.vMServerAdr == 0);
  199.         }
  200.  
  201.         // On the other hand, vMVolumeGrade was not introduced until
  202.         // version 2 of the GetVolParmsInfoBuffer, so we have to explicitly
  203.         // test whether we got a useful value.
  204.         
  205.         if (volumeBytesPerSecond != nil) {
  206.             bytesPerSecond = 0;
  207.             if (hpb.ioParam.ioActCount >= offsetof(GetVolParmsInfoBuffer, vMForeignPrivID)) {
  208.                 if (volParms.vMVolumeGrade <= 0) {
  209.                     bytesPerSecond = -volParms.vMVolumeGrade;
  210.                 }
  211.             }
  212.             *volumeBytesPerSecond = bytesPerSecond;
  213.         }
  214.     }
  215.     return err;
  216. }
  217.  
  218. static ByteCount BufferSizeForThisVolumeSpeed(UInt32 volumeBytesPerSecond)
  219.     // Calculate an appropriate copy buffer size based on the volumes
  220.     // rated speed.  Our target is to use a buffer that takes 0.25
  221.     // seconds to fill.  This is necessary because the volume might be
  222.     // mounted over a very slow link (like ARA), and if we do a 256 KB
  223.     // read over an ARA link we'll block the File Manager queue for
  224.     // so long that other clients (who might have innocently just
  225.     // called PBGetCatInfoSync) will block for a noticable amount of time.
  226.     //
  227.     // Note that volumeBytesPerSecond might be 0, in which case we assume
  228.     // some default value.
  229. {
  230.     ByteCount bufferSize;
  231.     
  232.     if (volumeBytesPerSecond == 0) {
  233.         bufferSize = kDefaultCopyBufferSize;
  234.     } else {
  235.         // We want to issue a single read that takes 0.25 of a second,
  236.         // so devide the bytes per second by 4.
  237.         bufferSize = volumeBytesPerSecond / 4;
  238.     }
  239.     
  240.     // Round bufferSize down to 512 byte boundary.
  241.     
  242.     bufferSize &= ~0x01FF;
  243.     
  244.     // Clip to sensible limits.
  245.     
  246.     if (bufferSize < kMinimumCopyBufferSize) {
  247.         bufferSize = kMinimumCopyBufferSize;
  248.     } else if (bufferSize > kMaximumCopyBufferSize) {
  249.         bufferSize = kMaximumCopyBufferSize;
  250.     }
  251.     return bufferSize;
  252. }
  253.  
  254. static ByteCount BufferSizeForThisVolume(FSVolumeRefNum vRefNum)
  255.     // This routine calculates the appropriate buffer size for
  256.     // the given vRefNum.  It's a simple composition of GetFileSystemInfo
  257.     // BufferSizeForThisVolumeSpeed.
  258. {
  259.     ByteCount volumeBytesPerSecond;
  260.     
  261.     if ( GetFileSystemInfo(vRefNum, nil, nil, &volumeBytesPerSecond) != noErr ) {
  262.         volumeBytesPerSecond = 0;
  263.     }
  264.     return BufferSizeForThisVolumeSpeed(volumeBytesPerSecond);
  265. }
  266.  
  267. /////////////////////////////////////////////////////////////////
  268. #pragma mark ----- True Async Tests -----
  269.  
  270. // The following code is use to determine whether the File Manager should
  271. // be called asynchronously or not by testing certain low-level functionality.
  272. // We do this because certain machines, like the Power Macintosh 6500, which
  273. // should support asynchronous I/O, don't do so in all circumstances.  Instead,
  274. // all local hard disk I/O is done synchronously.  This causes us problems, 
  275. // as described below.
  276. //
  277. // When you do synchronous I/O from an MP task, the actual I/O is done
  278. // asynchronously from a 'software interrupt' (technically known as a
  279. // SWI, pronounced "swee").  This SWI is just like a hardware
  280. // interrupt in that it can interrupt the current foreground task at any
  281. // instruction boundary (as long as the interrupt is not masked).  It also
  282. // (currently) executes with interrupts masked.
  283. //
  284. // If the underlying hardware does not work asynchronously, executing
  285. // asynchronous File Manager I/O from this SWI results in a very poor
  286. // user experience.  Specifically, interrupts remain masked for the duration
  287. // of the I/O, which causes the mouse to jerk horribly.
  288. //
  289. // So, our goal is to not do File Manager operations from MP tasks on machines
  290. // that have this problem.  We detect this in a three stage process, implemented
  291. // by the IsVolumeTrulyAsync routine.
  292. //
  293. // 1. Determine whether the volume's file system is asynchronous.  Some
  294. //    external file systems blow up if you call them asynchronously.
  295. //
  296. // 2. Determine whether the device driver thinks it runs asynchronously.   Some
  297. //    older hard disk device drivers just don't support asynchronous operations.
  298. //
  299. // 3. If the volume is local, determine whether the driver is really running
  300. //    asynchronously.  This is important on machines like the Power Macintosh
  301. //    6500, where the device driver is asynchronous but the ATA Manager isn't.
  302. //    We do this by issuing a large PBReadAsync and checking to see whether
  303. //    the I/O completion runs before the PBReadAsync returns.
  304. //
  305. // If all three of these checks give the green light on both the source and
  306. // destination volumes, we run the copying engine from an MP task.  If they 
  307. // don't, we do everything from system task level code.
  308. //
  309. // Note that somes of these checks aren't possible within a Carbon application.
  310. // This won't be a problem on Mac OS X (because all drivers are async there)
  311. // but it could be a problem for Carbon applications on traditional Mac OS.
  312. // I don't have a good solution to that problem at this time.
  313.  
  314. #if !TARGET_API_MAC_CARBON
  315.  
  316. // The following code implements part 3 of the true asynchronicity testing
  317. // algorithm, described above.  We 
  318.  
  319. volatile static Boolean gEarlyCompletion;
  320.  
  321. static IOCompletionUPP gMyCompletionUPP;        // points to MyCompletion
  322.  
  323. static void MyCompletion(ParmBlkPtr paramBlock)
  324.     // The I/O completion routine for the large read request we
  325.     // use for a true test of asynchronicity.
  326. {
  327.     #pragma unused(paramBlock)
  328.     
  329.     gEarlyCompletion = true;
  330. }
  331.  
  332. static OSStatus IsDeviceTrulyAsync(Boolean *async, UInt32 volumeBytesPerSecond, DriverRefNum refNum, SInt16 driveNum)
  333.     // This routine implements part 3 of the true asynchronicity testing
  334.     // algorithm, described above.
  335.     //
  336.     // We make the assumption that the device size is greater
  337.     // than or equal to kMaximumCopyBufferSize, which isn't necessarily
  338.     // true.  Should probably revisit that decision at some time.
  339. {
  340.     OSStatus err;
  341.     Ptr copyBuffer;
  342.     ParamBlockRec pb;
  343.     ByteCount bufferSize;
  344.  
  345.     // Calculate what we would consider to be a "large" request.
  346.     
  347.     bufferSize = BufferSizeForThisVolumeSpeed(volumeBytesPerSecond);
  348.  
  349.     MoreAssertQ(gMyCompletionUPP != nil);
  350.  
  351.     // Create the test buffer.
  352.     
  353.     copyBuffer = NewPtr(bufferSize);
  354.     err = MemError();
  355.     if (err == noErr) {
  356.         gEarlyCompletion = false;
  357.         
  358.         // Issue the request.
  359.         
  360.         pb.ioParam.ioCompletion = gMyCompletionUPP;
  361.         pb.ioParam.ioVRefNum    = driveNum;
  362.         pb.ioParam.ioRefNum     = refNum;
  363.         pb.ioParam.ioBuffer     = copyBuffer;
  364.         pb.ioParam.ioReqCount   = bufferSize;
  365.         pb.ioParam.ioPosMode    = fsFromStart;
  366.         pb.ioParam.ioPosOffset  = 0;
  367.  
  368.         err = PBReadAsync(&pb);
  369.         
  370.         // Did we complete early.  Note that this sort of code often
  371.         // needs to use an atomic test and set, but in this case that's
  372.         // not necessary because MyCompletion doesn't ever read
  373.         // gEarlyCompletion.
  374.         
  375.         *async = ! gEarlyCompletion;
  376.         
  377.         // Wait for the I/O to actually complete.
  378.         
  379.         while ( pb.ioParam.ioResult > 0 ) {
  380.             // do nothing
  381.         }
  382.  
  383.         // Clean up.
  384.         
  385.         DisposePtr( copyBuffer );
  386.         MoreAssertQ(MemError() == noErr);
  387.     }
  388.     return err;
  389. }
  390.  
  391. static OSStatus IsDeviceAsync(FSVolumeRefNum vRefNum, Boolean *async, DriverRefNum *refNum, SInt16 *driveNum)
  392.     // This routine asks the device driver whether it thinks it's asynchronous
  393.     // using Driver Gestalt.
  394. {
  395.     OSStatus err;
  396.     HParamBlockRec hpb;
  397.     DriverGestaltParam pb;
  398.     DriverFlags flags;
  399.     
  400.     // First find the driver and drive on which the volume is mounted.
  401.     
  402.     hpb.volumeParam.ioNamePtr = nil;
  403.     hpb.volumeParam.ioVRefNum = vRefNum;
  404.     hpb.volumeParam.ioVolIndex = 0;
  405.     err = PBHGetVInfoSync(&hpb);
  406.  
  407.     // Special case for offline volumes.  We're not going anywhere near them.
  408.     
  409.     if (err == noErr) {
  410.         if (hpb.volumeParam.ioVDrvInfo == 0) {
  411.             err = offLinErr;
  412.         }
  413.     }
  414.     if (err == noErr) {
  415.         *refNum   = hpb.volumeParam.ioVDRefNum;
  416.         *driveNum = hpb.volumeParam.ioVDrvInfo;
  417.     }
  418.     
  419.     // Then check whether the driver support Driver Gestalt.
  420.     
  421.     if (err == noErr) {
  422.         err = TradGetDriverInformation(*refNum, nil, &flags, nil, nil);
  423.     }
  424.     if (err == noErr) {
  425.         if (TradDriverGestaltIsOn(flags)) {
  426.             // If the driver supports Driver Gestalt, we ask it
  427.             // whether it thinks it supports async.
  428.             
  429.             pb.ioVRefNum = *driveNum;
  430.             pb.ioCRefNum = *refNum;
  431.             pb.csCode = kDriverGestaltCode;
  432.             pb.driverGestaltSelector = kdgSync;
  433.             err = PBStatusSync( (ParamBlockRec *) &pb);
  434.             if (err == noErr) {
  435.                 *async = ! (GetDriverGestaltSyncResponse(&pb)->behavesSynchronously);
  436.             }
  437.         } else {
  438.             // If the driver doesn't support Driver Gestalt,
  439.             // we return true.  This will cause the caller
  440.             // to try a real test based on I/O.  We do this
  441.             // because certain really old drivers, like ".Sony",
  442.             // are async even though they don't support Driver
  443.             // Gestalt.
  444.             
  445.             *async = true;
  446.         }
  447.     }
  448.     return err;
  449. }
  450.  
  451. #endif
  452.  
  453. static OSStatus IsVolumeTrulyAsync(FSVolumeRefNum vRefNum, Boolean *async)
  454.     // This routine sets *async to true if the volume can, within
  455.     // the bounds of our ability to tell, operate asynchronously.
  456.     // The algorithm is described above.
  457. {
  458.     OSStatus err;
  459.     Boolean localVolume;
  460.     UInt32  volumeBytesPerSecond;
  461.  
  462.     err = GetFileSystemInfo(vRefNum, async, &localVolume, &volumeBytesPerSecond);
  463.     #if TARGET_API_MAC_CARBON
  464.         // To a first approximation, all device drivers are async on Carbon.
  465.         // Actually, we still have the Power Mac 6500 problem on Carbon on
  466.         // traditional Mac OS, but I'm going to ignore that for the moment.
  467.     #else
  468.         {
  469.             DriverRefNum refNum;
  470.             SInt16 driveNum;
  471.  
  472.             if (err == noErr && *async) {
  473.                 err = IsDeviceAsync(vRefNum, async, &refNum, &driveNum);
  474.             }
  475.             if (err == noErr && *async && localVolume) {
  476.                 err = IsDeviceTrulyAsync(async, volumeBytesPerSecond, refNum, driveNum);
  477.             }
  478.         }
  479.     #endif
  480.     return err;
  481. }
  482.  
  483. /////////////////////////////////////////////////////////////////
  484. #pragma mark ----- Copy Engine -----
  485.  
  486. struct CopyParams {
  487.     void             *copyBuffer;
  488.     ByteCount         copyBufferSize;
  489.     UInt32            recursionLimit;
  490.     UInt32            currentDepth;
  491.     Boolean         copyingToDropFolder;
  492.     Boolean            copyingToLocalVolume;
  493. };
  494. typedef struct CopyParams CopyParams;
  495.  
  496. // The tasks stack size is a combination of two factors:
  497. //
  498. // 1. Non-recursive overhead, about 3 KB, which is the
  499. //    sum of the frame sizes of the various non-recursive
  500. //    routines in the maximum depth copy engine call chain.
  501. //
  502. // 2. Recursive overhead, about 200 bytes per recursion level,
  503. //    which is the frame size of the CopyFolder routine.
  504. //
  505. // With the current numbers, this works out to less than 24 KB,
  506. // which is not a problem.
  507.  
  508. enum {
  509.     kCopyFolderFrameSize = 208,
  510.     
  511.     kCopyFileTaskStackSize = (3 * 1024) + (kMaximumFolderDepth * kCopyFolderFrameSize)
  512. };                        
  513.  
  514. static UTCDateTime gMagicBusyCreationDate;        // a UTCDateTime equivalent of kMagicBusyCreationDate.
  515.  
  516. static HFSUniStr255 gDataForkName;
  517. static HFSUniStr255 gRsrcForkName;
  518.  
  519. static OSStatus CopyFork(const FSRef *source, SInt64 sourceForkSize, const FSRef *dest, 
  520.                             SInt16 forkDestRefNum,
  521.                             ConstHFSUniStr255Param forkName,
  522.                             const CopyParams *params)
  523.     // Copies the fork whose name is forkName from source to dest.
  524.     // A refnum for the destination fork may be supplied in forkDestRefNum.
  525.     // If forkDestRefNum is 0, we must open the destination fork ourselves,
  526.     // otherwise it has been opened for us and we shouldn't close it.
  527.     //
  528.     // Frame Size: 128
  529. {
  530.     OSStatus err;
  531.     OSStatus err2;
  532.     SInt16 sourceRef;
  533.     SInt16 destRef;
  534.     SInt64 bytesRemaining;
  535.     SInt64 bytesToReadThisTime;
  536.     SInt64 bytesToWriteThisTime;
  537.     
  538.     // If we haven't been passed in a forkDestRefNum (which basically
  539.     // means we're copying into a non-drop folder), create the destination
  540.     // fork.  We have to do this regardless of whether sourceForkSize is 
  541.     // 0, because we want to preserve empty forks.
  542.     
  543.     if (forkDestRefNum == 0) {
  544.         err = FSCreateFork(dest, forkName->length, forkName->unicode);
  545.         
  546.         // Mac OS 9.0 has a bug (in the AppleShare external file system,
  547.         // I think) [2410374] that causes FSCreateFork to return an errFSForkExists
  548.         // error even though the fork is empty.  The following code swallows
  549.         // the error (which is harmless) in that case.
  550.         
  551.         if (err == errFSForkExists && !params->copyingToLocalVolume) {
  552.             err = noErr;
  553.         }
  554.     }
  555.  
  556.     // The remainder of this code only applies if there is actual data
  557.     // in the source fork.
  558.     
  559.     if (err == noErr && sourceForkSize != 0) {
  560.  
  561.         // Prepare for failure.
  562.         
  563.         sourceRef = 0;
  564.         destRef   = 0;
  565.  
  566.         // Open up the destination fork, if we're asked to, otherwise
  567.         // just use the passed in forkDestRefNum.
  568.         
  569.         if (forkDestRefNum == 0) {
  570.             err = FSOpenFork(dest, forkName->length, forkName->unicode, fsWrPerm, &destRef);
  571.         } else {
  572.             destRef = forkDestRefNum;
  573.         }
  574.         
  575.         // Open up the source fork.
  576.         
  577.         if (err == noErr) {
  578.             err = FSOpenFork(source, forkName->length, forkName->unicode, fsRdPerm, &sourceRef);
  579.         }
  580.  
  581.         // Here we create space for the entire fork on the destination volume.
  582.         // FSAllocateFork has the right semantics on both traditional Mac OS
  583.         // and Mac OS X.  On traditional Mac OS it will allocate space for the
  584.         // file in one hit without any other special action.  On Mac OS X,
  585.         // FSAllocateFork is preferable to FSSetForkSize because it prevents
  586.         // the system from zero filling the bytes that were added to the end
  587.         // of the fork (which would be waste becasue we're about to write over
  588.         // those bytes anyway.
  589.         
  590.         if (err == noErr) {
  591.             err = FSAllocateFork(destRef, kFSAllocNoRoundUpMask, fsFromStart, 0, sourceForkSize, nil);
  592.         }
  593.  
  594.         // Copy the file from the source to the destination in chunks of
  595.         // no more than params->copyBufferSize bytes.  This is fairly
  596.         // boring code except for the bytesToReadThisTime/bytesToWriteThisTime
  597.         // distinction.  On the last chunk, we round bytesToWriteThisTime
  598.         // up to the next 512 byte boundary and then, after we exit the loop,
  599.         // we set the file's EOF back to the real location (if the fork size
  600.         // is not a multiple of 512 bytes).
  601.         // 
  602.         // This technique works around a 'bug' in the traditional Mac OS File Manager,
  603.         // where the File Manager will put the last 512-byte block of a large write into
  604.         // the cache (even if we specifically request no caching) if that block is not
  605.         // full. If the block goes into the cache it will eventually have to be
  606.         // flushed, which causes sub-optimal disk performance.
  607.         //
  608.         // This is only done if the destination volume is local.  For a network
  609.         // volume, it's better to just write the last bytes directly.
  610.         //
  611.         // This is extreme over-optimisation given the other limits of this
  612.         // sample, but I will hopefully get to the other limits eventually.
  613.         
  614.         bytesRemaining = sourceForkSize;
  615.         while (err == noErr && bytesRemaining != 0) {
  616.             if (bytesRemaining > params->copyBufferSize) {
  617.                 bytesToReadThisTime  = params->copyBufferSize;
  618.                 bytesToWriteThisTime = bytesToReadThisTime;
  619.             } else {
  620.                 bytesToReadThisTime  = bytesRemaining;
  621.                 if (params->copyingToLocalVolume) {
  622.                     bytesToWriteThisTime = (bytesRemaining + 0x01FF) & ~0x01FF;
  623.                 } else {
  624.                     bytesToWriteThisTime = bytesRemaining;
  625.                 }
  626.             }
  627.             err = FSReadFork(sourceRef, fsAtMark + noCacheMask, 0, bytesToReadThisTime, params->copyBuffer, nil);
  628.             if (err == noErr) {
  629.                 err = FSWriteFork(destRef, fsAtMark + noCacheMask, 0, bytesToWriteThisTime, params->copyBuffer, nil);
  630.             }
  631.             if (err == noErr) {
  632.                 bytesRemaining -= bytesToReadThisTime;
  633.             }
  634.         }
  635.         if (err == noErr && (params->copyingToLocalVolume && ((sourceForkSize & 0x01FF) != 0)) ) {
  636.             err = FSSetForkSize(destRef, fsFromStart, sourceForkSize);
  637.         }
  638.  
  639.         // Clean up.
  640.         
  641.         if (sourceRef != 0) {
  642.             err2 = FSCloseFork(sourceRef);
  643.             MoreAssertQ(err2 == noErr);
  644.             if (err == noErr) {
  645.                 err = err2;
  646.             }
  647.         }
  648.  
  649.         // Only close destRef if we were asked to open it (ie forkDestRefNum == 0) and
  650.         // we actually managed to open it (ie destRef != 0).
  651.         
  652.         if (forkDestRefNum == 0 && destRef != 0) {
  653.             err2 = FSCloseFork(destRef);
  654.             MoreAssertQ(err2 == noErr);
  655.             if (err == noErr) {
  656.                 err = err2;
  657.             }
  658.         }
  659.     }
  660.  
  661.     return err;
  662. }
  663.  
  664. // The ForkTracker data structure holds information about a specific fork,
  665. // specifically the name and the refnum.  We use this to build a list of
  666. // all the forks before we start copying them.  We need to do this because,
  667. // if we're copying into a drop folder, we must open all the forks before
  668. // we start copying data into any of them.
  669. //
  670. // For debug builds, we pad the ForkTracker record out to be a multiple of
  671. // 8 bytes long.  This is because the debug builds use MPGetAllocatedBlockSize
  672. // in many assertions, but MPGetAllocatedBlockSize only returns the size of
  673. // the block to an 8 byte resolution.  Rather than second guess its results,
  674. // we just ensure that the ForkTracker element size for debug build is
  675. // a multiple of 8.  Also, we have to use 68K alignment because that's
  676. // the only guaranteed way to force a specific structure layout.
  677.  
  678. #pragma options align=mac68k
  679.  
  680. struct ForkTracker {
  681.     HFSUniStr255     forkName;
  682.     SInt64            forkSize;
  683.     SInt16           forkDestRefNum;
  684.     #if MORE_DEBUG
  685.         SInt16         pad[3];
  686.     #endif
  687. };
  688. typedef struct ForkTracker ForkTracker;
  689. typedef ForkTracker *ForkTrackerPtr;
  690.  
  691. #pragma options align=reset
  692.  
  693. static Boolean IdenticalHFSUniStr255(const HFSUniStr255 *lhs, const HFSUniStr255 *rhs)
  694.     // MP Note:  I can call memcmp here because it's implement in 
  695.     // a library that's statically linked in to our application and
  696.     // I checked the code in that library and it doesn't break the
  697.     // rules for pre-emptive tasks.
  698.     //
  699.     // Frame Size: 80
  700. {
  701.     return (lhs->length == rhs->length)
  702.         && (memcmp(lhs->unicode, rhs->unicode, lhs->length * sizeof(UniChar)) == 0);
  703. }
  704.  
  705. static OSStatus CalculateForksToCopy(const FSRef *source,
  706.                                         SInt64 *dataForkSize,
  707.                                         SInt64 *rsrcForkSize,
  708.                                         ForkTrackerPtr *otherForksParam,
  709.                                         ItemCount      *otherForksCountParam)
  710.     // This routine determines the list of forks that a file has.
  711.     // dataFork is set if the file has a data fork.
  712.     // rsrcFork is set if the file has a resource fork.
  713.     // otherForksParam is set to point to a memory block allocated with
  714.     // MPAllocAligned if the file has forks beyond the resource and data
  715.     // forks.  You must free that block with MPFree.  otherForksCountParam
  716.     // is set to the count of the number of forks in the otherForksParam
  717.     // array.  This count does *not* include the resource and data forks.
  718.     //
  719.     // Frame Size: 656
  720. {
  721.     OSStatus        err;
  722.     Boolean         done;
  723.     CatPositionRec  iterator;
  724.     HFSUniStr255     thisForkName;
  725.     SInt64          thisForkSize;
  726.     ForkTrackerPtr  otherForks;
  727.     ItemCount       otherForksCount;
  728.     ItemCount       otherForksMemoryBlockCount;
  729.     #if MORE_DEBUG
  730.         Boolean            doneDataFork = false;
  731.         Boolean            doneRsrcFork = false;
  732.     #endif
  733.     
  734.     MoreAssertQ(source != nil);
  735.     MoreAssertQ(dataForkSize != nil);
  736.     MoreAssertQ(rsrcForkSize != nil);
  737.     MoreAssertQ(otherForksParam != nil);
  738.     MoreAssertQ(*otherForksParam == nil);
  739.     MoreAssertQ(otherForksCountParam != nil);
  740.     MoreAssertQ(*otherForksCountParam == 0);
  741.     
  742.     MoreAssertQ(sizeof(ForkTracker) % 8 == 0);        // See note above.
  743.     
  744.     *dataForkSize = 0;
  745.     *rsrcForkSize = 0;
  746.     
  747.     otherForks = nil;
  748.     otherForksCount = 0;
  749.  
  750.     // Iterate through the list of forks, processing each fork name in turn.
  751.     
  752.     iterator.initialize = 0;
  753.     done = false;
  754.     do {
  755.         err = FSIterateForks(source, &iterator, &thisForkName, &thisForkSize, nil);
  756.         if (err == errFSNoMoreItems) {
  757.             err = noErr;
  758.             done = true;
  759.         } else if (err == noErr) {
  760.             if ( kSpecialCaseClassicForks && IdenticalHFSUniStr255(&thisForkName, &gDataForkName) ) {
  761.                 #if MORE_DEBUG
  762.                     // only one data fork, please
  763.                     MoreAssertQ(doneDataFork == false);
  764.                     doneDataFork = true;
  765.                 #endif
  766.                 *dataForkSize = thisForkSize;
  767.             } else if ( kSpecialCaseClassicForks && IdenticalHFSUniStr255(&thisForkName, &gRsrcForkName) ) {
  768.                 #if MORE_DEBUG
  769.                     // only one resource fork, please
  770.                     MoreAssertQ(doneRsrcFork == false);
  771.                     doneRsrcFork = true;
  772.                 #endif
  773.                 *rsrcForkSize = thisForkSize;
  774.             } else {
  775.  
  776.                 // We've found a fork other than the resource and data forks.
  777.                 // We have to add it to the otherForks array.  But the array
  778.                 // a) may not have been created yet, and b) may not contain
  779.                 // enough elements to hold the new fork.
  780.                 
  781.                 if (otherForks == nil) {
  782.                 
  783.                     // The array hasn't been allocated yet, allocate it.
  784.                     
  785.                     otherForksMemoryBlockCount = kExpectedForkCount;
  786.                     otherForks = MPAllocateAligned(sizeof(ForkTracker) * kExpectedForkCount, kMPAllocateDefaultAligned, kNilOptions);
  787.                     if (otherForks == nil) {
  788.                         err = memFullErr;
  789.                     }
  790.                 } else {
  791.                 
  792.                     // If the array doesn't contain enough elements, grow it.
  793.                     
  794.                     if (otherForksCount == otherForksMemoryBlockCount) {
  795.                         ForkTrackerPtr newOtherForks;
  796.                         
  797.                         newOtherForks = MPAllocateAligned(sizeof(ForkTracker) * (otherForksCount + kExpectedForkCount), kMPAllocateDefaultAligned, kNilOptions);
  798.                         if (otherForks == nil) {
  799.                             err = memFullErr;
  800.                         } else {
  801.                             BlockMoveData(otherForks, newOtherForks, sizeof(ForkTracker) * otherForksCount);
  802.                             otherForksMemoryBlockCount += kExpectedForkCount;
  803.                             MPFree(otherForks);
  804.                             otherForks = newOtherForks;
  805.                         }
  806.                     }
  807.                 }
  808.                 
  809.                 // If we have no error, we know we have space in the otherForks
  810.                 // array to place the new fork.  Put it there and increment the
  811.                 // count of forks.
  812.                 
  813.                 if (err == noErr) {
  814.                     MoreAssertQ(otherForks != nil);
  815.                     MoreAssertQ(otherForksCount < otherForksMemoryBlockCount);
  816.                     MoreAssertQ(MPGetAllocatedBlockSize(otherForks) == sizeof(ForkTracker) * otherForksMemoryBlockCount);
  817.                     
  818.                     BlockMoveData(&thisForkName, &otherForks[otherForksCount].forkName, sizeof(thisForkName));
  819.                     otherForks[otherForksCount].forkSize = thisForkSize;
  820.                     otherForks[otherForksCount].forkDestRefNum = 0;
  821.                     otherForksCount += 1;
  822.                 }
  823.             }
  824.         }
  825.     } while (err == noErr && ! done);
  826.     
  827.     // Clean up.
  828.     
  829.     if (err != noErr) {
  830.         if (otherForks != nil) {
  831.             MPFree(otherForks);
  832.             otherForks = nil;
  833.         }
  834.         otherForksCount = 0;
  835.     }
  836.     *otherForksParam = otherForks;
  837.     *otherForksCountParam = otherForksCount;
  838.  
  839.     // I had to break up the post-conditions into multiple statements
  840.     // because otherwise they overflow the string length limit of the pre-processor.
  841.     // Jeff Rohl would be proud.
  842.     
  843.     if (err == noErr) {
  844.         // Success post-condition
  845.         MoreAssertQ(   ((otherForks == nil) && (otherForksCount == 0)) 
  846.                     || ((otherForks != nil) && (otherForksCount <= otherForksMemoryBlockCount) && (MPGetAllocatedBlockSize(otherForks) == sizeof(ForkTracker) * otherForksMemoryBlockCount)));
  847.     } else {
  848.         // Failure post-condition
  849.         MoreAssertQ((otherForks == nil) && (otherForksCount == 0));
  850.     }
  851.  
  852.     return err;    
  853. }
  854.  
  855. static OSStatus OpenAllForks(const FSRef *dest, Boolean openDataFork, Boolean openRsrcFork, 
  856.                             SInt16 *dataRefNum, SInt16 *rsrcRefNum, ForkTrackerPtr otherForks, ItemCount otherForksCount)
  857.     // Open all the forks of the file.  We need to do this when we're copying
  858.     // into a drop folder, where you must open all the forks before starting
  859.     // to write to any of them.
  860.     //
  861.     // IMPORTANT:  If it fails, this routine won't close forks that opened successfully.
  862.     // You must call CloseAllForks regardless of whether this routine returns an error.
  863.     //
  864.     // Frame Size: 96
  865. {
  866.     OSStatus  err;
  867.     ItemCount thisForkIndex;
  868.  
  869.     MoreAssertQ(dataRefNum != nil);    
  870.     MoreAssertQ(rsrcRefNum != nil);    
  871.     MoreAssertQ(*dataRefNum == 0);
  872.     MoreAssertQ(*rsrcRefNum == 0);
  873.     MoreAssertQ((otherForks == nil) || (otherForksCount > 0));
  874.  
  875.     // Open the resource and data forks as a special case.
  876.     
  877.     err = noErr;
  878.     if (openDataFork) {
  879.         // Data fork never needs to be created, so I don't have to FSCreateFork it here.
  880.         err = FSOpenFork(dest, gDataForkName.length, gDataForkName.unicode, fsWrPerm, dataRefNum);
  881.     }
  882.     if (err == noErr && openRsrcFork) {
  883.         // Resource fork never needs to be created, so I don't have to FSCreateFork it here.
  884.         err = FSOpenFork(dest, gRsrcForkName.length, gRsrcForkName.unicode, fsWrPerm, rsrcRefNum);
  885.     }
  886.     
  887.     // Open the other forks.
  888.     
  889.     if (err == noErr) {
  890.         for (thisForkIndex = 0; thisForkIndex < otherForksCount; thisForkIndex++) {
  891.             MoreAssertQ(otherForks[thisForkIndex].forkDestRefNum == 0);
  892.  
  893.             // Create the fork.  Swallow afpAccessDenied because this operation
  894.             // causes the external file system compatibility shim in Mac OS 9 to
  895.             // generate a GetCatInfo request to the AppleShare external file system,
  896.             // which in turn causes an AFP GetFileDirParms request on the wire,
  897.             // which the AFP server bounces with afpAccessDenied because the file
  898.             // is in a drop folder.  As there's no native support for non-classic
  899.             // forks in current AFP, there's no way I can decide how I should
  900.             // handle this in a non-test case.  So I just swallow the error and
  901.             // hope that when native AFP support arrives, the right thing will happen.
  902.             
  903.             err = FSCreateFork(dest, otherForks[thisForkIndex].forkName.length, otherForks[thisForkIndex].forkName.unicode);
  904.             if (err == noErr || err == afpAccessDenied) {
  905.                 err = noErr;
  906.             }
  907.             
  908.             // Previously I avoided opening up the fork if the fork if the
  909.             // length was empty, but that confused CopyFork into thinking
  910.             // this wasn't a drop folder copy, so I decided to simply avoid
  911.             // this trivial optimisation.  In drop folders, we always open
  912.             // all forks.
  913.             
  914.             if (err == noErr) {
  915.                 err = FSOpenFork(dest, otherForks[thisForkIndex].forkName.length, otherForks[thisForkIndex].forkName.unicode, fsWrPerm, &otherForks[thisForkIndex].forkDestRefNum);
  916.             }
  917.             if (err != noErr) {
  918.                 break;
  919.             }
  920.         }
  921.     }
  922.     return err;
  923. }
  924.  
  925. static OSStatus CloseAllForks(SInt16 dataRefNum, SInt16 rsrcRefNum, ForkTrackerPtr otherForks, ItemCount otherForksCount)
  926.     // Close all the forks that might have been opened by OpenAllForks.
  927.     //
  928.     // Frame Size: 96
  929. {
  930.     OSStatus  err;
  931.     OSStatus  err2;
  932.     ItemCount thisForkIndex;
  933.  
  934.     MoreAssertQ((otherForks == nil) || (otherForksCount > 0));
  935.  
  936.     err = noErr;
  937.     if (dataRefNum != 0) {
  938.         err2 = FSCloseFork(dataRefNum);
  939.         MoreAssertQ(err2 == noErr);
  940.         if (err == noErr) {
  941.             err = err2;
  942.         }
  943.     }
  944.     if (rsrcRefNum != 0) {
  945.         err2 = FSCloseFork(rsrcRefNum);
  946.         MoreAssertQ(err2 == noErr);
  947.         if (err == noErr) {
  948.             err = err2;
  949.         }
  950.     }
  951.     for (thisForkIndex = 0; thisForkIndex < otherForksCount; thisForkIndex++) {
  952.         if (otherForks[thisForkIndex].forkDestRefNum != 0) {
  953.             err2 = FSCloseFork(otherForks[thisForkIndex].forkDestRefNum);
  954.             MoreAssertQ(err2 == noErr);
  955.             if (err == noErr) {
  956.                 err = err2;
  957.             }
  958.         }
  959.     }
  960.     return err;
  961. }
  962.  
  963. static OSStatus CopyItemsForks(const FSRef *source, const FSRef *dest, CopyParams *params)
  964.     // Copy all the folks of a file or folder from source to dest.
  965.     //
  966.     // Frame Size: 112 (+ 656 in sub-routines)
  967. {
  968.     OSStatus         err;
  969.     OSStatus         err2;
  970.     SInt64          dataForkSize;
  971.     SInt64          rsrcForkSize;
  972.     ForkTrackerPtr     otherForks;
  973.     ItemCount          otherForksCount;
  974.     SInt16             dataRefNum;
  975.     SInt16             rsrcRefNum;
  976.     ItemCount         thisForkIndex;
  977.     
  978.     dataRefNum = 0;
  979.     rsrcRefNum = 0;
  980.     otherForks = nil;
  981.     otherForksCount = 0;
  982.     
  983.     // First determine the list of forks that the source has.
  984.     
  985.     err = CalculateForksToCopy(source, &dataForkSize, &rsrcForkSize, &otherForks, &otherForksCount);
  986.     if (err == noErr) {
  987.     
  988.         // If we're copying into a drop folder, open up all of those forks.
  989.         // We have to do this because, once we've starting writing to a fork
  990.         // in a drop folder, we can't open any more forks.
  991.         //
  992.         // We only do this if we're copying into a drop folder in order
  993.         // to conserve FCBs in the more common, non-drop folder case.
  994.         
  995.         if (params->copyingToDropFolder) {
  996.             err = OpenAllForks(dest, (dataForkSize != 0), (rsrcForkSize != 0), &dataRefNum, &rsrcRefNum, otherForks, otherForksCount);
  997.         }
  998.         
  999.         // Copy each fork.
  1000.         
  1001.         if (err == noErr && (dataForkSize != 0)) {
  1002.             err = CopyFork(source, dataForkSize, dest, dataRefNum, &gDataForkName, params);
  1003.         }
  1004.         if (err == noErr && (rsrcForkSize != 0)) {
  1005.             err = CopyFork(source, rsrcForkSize, dest, rsrcRefNum, &gRsrcForkName, params);
  1006.         }
  1007.         if (err == noErr) {
  1008.             for (thisForkIndex = 0; thisForkIndex < otherForksCount; thisForkIndex++) {
  1009.                 err = CopyFork(source, otherForks[thisForkIndex].forkSize, 
  1010.                                 dest, otherForks[thisForkIndex].forkDestRefNum, &otherForks[thisForkIndex].forkName, params);
  1011.                 if (err != noErr) {
  1012.                     break;
  1013.                 }
  1014.             }
  1015.         }
  1016.         
  1017.         // Close any forks that might be left open.  Note that we have to call
  1018.         // this regardless of an error.  Also note that this only closes forks
  1019.         // that were opened by OpenAllForks.  If we're not copying into a drop
  1020.         // folder, the forks are opened and closed by CopyFork.
  1021.         
  1022.         err2 = CloseAllForks(dataRefNum, rsrcRefNum, otherForks, otherForksCount);
  1023.         if (err == noErr) {
  1024.             err = err2;
  1025.         }
  1026.     }
  1027.  
  1028.     // Clean up.
  1029.     
  1030.     if (otherForks != nil) {
  1031.         MPFree(otherForks);
  1032.     }
  1033.  
  1034.     return err;
  1035. }
  1036.  
  1037. static volatile UInt32 gState = 0;        // Used only during debugging.
  1038.  
  1039. static Boolean gFileSharingIsRunning;
  1040.  
  1041. static OSStatus CopyFile(const FSRef *source, FSCatalogInfo *sourceCatInfo,
  1042.                             const FSRef *destDir, ConstHFSUniStr255Param destName,
  1043.                             CopyParams *params)
  1044.     // Copies a file referenced by source to the directory referenced by
  1045.     // destDir.  destName is the name the file should be given in the
  1046.     // destination directory.  sourceCatInfo is the catalogue info of
  1047.     // the file, which is passed in as an optimisation (we could get it
  1048.     // by doing a FSGetCatalogInfo but the caller has already done that
  1049.     // so we might as well take advantage of that).
  1050.     //
  1051.     // Frame Size: 176 (+ 112 + 656 in sub-routines)
  1052. {
  1053.     OSStatus err;
  1054.     OSStatus junk;
  1055.     FSRef     dest;
  1056.     OSType   originalFileType;
  1057.     UInt16   originalNodeFlags;
  1058.     UTCDateTime originalCreateDate;
  1059.  
  1060.     // If we're copying to a drop folder, we won't be able to reset this
  1061.     // information once the copy is done, so we don't mess it up in
  1062.     // the first place.  We still clear the locked bit though; items dropped
  1063.     // into a drop folder always become unlocked.
  1064.     
  1065.     if (!params->copyingToDropFolder) {
  1066.         // Remember and clear the file's type, so the Finder doesn't
  1067.         // look at the file until we're done.
  1068.         
  1069.         originalFileType = ((FInfo *) &sourceCatInfo->finderInfo)->fdType;
  1070.         ((FInfo *) &sourceCatInfo->finderInfo)->fdType = kFirstMagicBusyFiletype;
  1071.  
  1072.         // Set the file's creation date to kMagicBusyCreationDate,
  1073.         // remembering the old value for restoration later.
  1074.         
  1075.         originalCreateDate = sourceCatInfo->createDate;
  1076.         sourceCatInfo->createDate = gMagicBusyCreationDate;
  1077.  
  1078.         // Remember and clear the file's locked status, so that we can
  1079.         // actually write the forks we're about to create.
  1080.         
  1081.         originalNodeFlags = sourceCatInfo->nodeFlags;
  1082.     }
  1083.     sourceCatInfo->nodeFlags &= ~kFSNodeLockedMask;
  1084.     
  1085.     // Create the file in destDir.
  1086.     
  1087.     // In Mac OS 9.0, File Sharing messes up calls to FSCreateFileUnicode such that,
  1088.     // if you pass in sourceCatInfo, the file will be created with a blank file
  1089.     // type and creator code.  [2397324]  I work around this by, if File
  1090.     // Sharing is running, splitting the create into two parts: first create the file,
  1091.     // then set the catalogue info.  This defeats the purpose of the unified call (ie
  1092.     // the operation isn't atomic) but them's the breaks.  Also, I found that if you
  1093.     // set the catalogue info directly after creating the file, you trigger another
  1094.     // aspect of this File Sharing problem and you end up with weird zombie files that
  1095.     // are half locked and half unlocked.  To solve this I don't set any catalogue
  1096.     // info here and set all the info at the end of the routine, where normally I just
  1097.     // restore the modification dates etc.
  1098.     //
  1099.     // This should be OK because the FSCreateFileUnicode creates a file with a zero
  1100.     // fdType, which the Finder will ignore.
  1101.     //
  1102.     // I'm scared now.
  1103.     
  1104.     if ( ! gFileSharingIsRunning || params->copyingToDropFolder) {
  1105.         // File sharing not running, let's do this the right way.
  1106.         err = FSCreateFileUnicode(destDir, destName->length, destName->unicode, kFSCatInfoSettableInfo, sourceCatInfo, &dest, nil);
  1107.     } else {
  1108.         // File sharing is running, use the workaround.
  1109.         err = FSCreateFileUnicode(destDir, destName->length, destName->unicode, kFSCatInfoNone, nil, &dest, nil);
  1110.     }
  1111.     
  1112.     // Iterate through the forks, copying each fork.
  1113.     
  1114.     if (err == noErr) {
  1115.         err = CopyItemsForks(source, &dest, params);
  1116.  
  1117.         // Restore the original file type, creation and modification dates, and
  1118.         // locked status.  This is one of the places where we need to handle drop
  1119.         // folders as a special case because this FSSetCatalogInfo will fail for
  1120.         // an item in a drop folder, so we don't even attempt it.
  1121.         //
  1122.         // Also, if File Sharing is running we use this point to set up all
  1123.         // the catalogue info.  
  1124.         
  1125.         if (err == noErr && !params->copyingToDropFolder) {
  1126.             ((FInfo *) &sourceCatInfo->finderInfo)->fdType = originalFileType;
  1127.             sourceCatInfo->createDate = originalCreateDate;
  1128.             sourceCatInfo->nodeFlags  = originalNodeFlags;
  1129.             if (gFileSharingIsRunning) {
  1130.                 err = FSSetCatalogInfo(&dest, kFSCatInfoCreateDate
  1131.                                             | kFSCatInfoAttrMod 
  1132.                                             | kFSCatInfoContentMod 
  1133.                                             | kFSCatInfoFinderInfo 
  1134.                                             | kFSCatInfoNodeFlags, sourceCatInfo);
  1135.             } else {
  1136.                 err = FSSetCatalogInfo(&dest, kFSCatInfoSettableInfo, sourceCatInfo);
  1137.             }
  1138.         }
  1139.         
  1140.         // If we created the file and the copy failed, try to clean up by
  1141.         // deleting the file we created.  We do this because, while it's
  1142.         // possible for the copy to fail halfway through and we don't really
  1143.         // clean up that well, we *really* don't wan't any half-created
  1144.         // files being left around.
  1145.         //
  1146.         // Note that there are cases where the assert can fire which are not
  1147.         // errors (for example, if the  destination is in a drop folder) but
  1148.         // I'll leave it in anyway because I'm interested in discovering those
  1149.         // cases.  Note that, if this fires and we're running MP, current versions
  1150.         // of MacsBug won't catch the exception and the MP task will terminate
  1151.         // with an kMPTaskAbortedErr error.
  1152.         
  1153.         if (err != noErr) {
  1154.             junk = FSDeleteObject(&dest);
  1155.             MoreAssertQ(junk == noErr);
  1156.         }
  1157.     }
  1158.     
  1159.     return err;
  1160. }
  1161.  
  1162. static OSStatus CopyFolder(const FSRef *source, FSCatalogInfo *sourceCatInfo,
  1163.                             const FSRef *destDir, ConstHFSUniStr255Param destName,
  1164.                             CopyParams *params)
  1165.     // Copies a folder referenced by source to the directory referenced by
  1166.     // destDir.  destName is the name the folder should be given in the
  1167.     // destination directory.  sourceCatInfo is the catalogue info of
  1168.     // the folder, which is passed in as an optimisation (we could get it
  1169.     // by doing a FSGetCatalogInfo but the caller has already done that
  1170.     // so we might as well take advantage of that).
  1171.     //
  1172.     // IMPORTANT:  This routine calls itself recursively, so adding local
  1173.     // variables can significantly effect the stack usage of the operation.
  1174.     // If you add local variables, remember to update the constant
  1175.     // kCopyFolderFrameSize to be greater than or equal to the real frame size.
  1176.     //
  1177.     // Frame Size: 208 (see kCopyFolderFrameSize) (+ 176 + 112 + 656 in sub-routines)
  1178. {
  1179.     OSStatus       err;                    // stack,  4 bytes
  1180.     OSStatus       junk;                    // stack,  4 bytes
  1181.     FSRef           newDir;                // stack, 80 bytes
  1182.     FSIterator       iterator;                // stack,  4 bytes
  1183.     ItemCount       foundItems;            // stack,  4 bytes
  1184.     FSCatalogInfo *foundCatInfos;        // heap, 144 bytes per element
  1185.     FSRef           *foundFSRefs;            // heap,  80 bytes per element
  1186.     HFSUniStr255  *foundNames;            // heap, 512 bytes per element
  1187.     Boolean          done;                    // stack,  4 bytes
  1188.     ItemCount       i;                    // stack,  4 bytes
  1189.     UTCDateTime   originalCreateDate;    // stack,  8 bytes
  1190.  
  1191.     // Check that we haven't run off the bottom of our stack.
  1192.     
  1193.     err = noErr;
  1194.     params->currentDepth += 1;
  1195.     if (params->currentDepth > params->recursionLimit) {
  1196.         err = -1;
  1197.     }
  1198.  
  1199.     // Allocate memory for temporary buffers.  
  1200.     //
  1201.     // 736 bytes per element per kFolderItemsPerBulkCall (currently 20) per folder nesting depth
  1202.     
  1203.     if (err == noErr) {
  1204.         foundCatInfos = MPAllocateAligned(kFolderItemsPerBulkCall * sizeof(FSCatalogInfo),
  1205.                                             kMPAllocateDefaultAligned, kNilOptions);
  1206.         foundFSRefs   = MPAllocateAligned(kFolderItemsPerBulkCall * sizeof(FSRef),
  1207.                                             kMPAllocateDefaultAligned, kNilOptions);
  1208.         foundNames    = MPAllocateAligned(kFolderItemsPerBulkCall * sizeof(HFSUniStr255),
  1209.                                             kMPAllocateDefaultAligned, kNilOptions);
  1210.         if (foundCatInfos == nil || foundFSRefs == nil || foundNames == nil) {
  1211.             err = memFullErr;
  1212.         }
  1213.     }
  1214.  
  1215.     // Create the destination directory.
  1216.     
  1217.     if (err == noErr) {
  1218.  
  1219.         // Set the folder's creation date to kMagicBusyCreationDate
  1220.         // so that the Finder doesn't mess with the folder while
  1221.         // it's copying.  We remember the old value for restoration
  1222.         // later.  We only do this if we're not copying to a drop
  1223.         // folder, because if we are copying to a drop folder we don't
  1224.         // have the opportunity to reset the information at the end of
  1225.         // this routine.
  1226.         
  1227.         if (!params->copyingToDropFolder) {
  1228.             originalCreateDate = sourceCatInfo->createDate;
  1229.             sourceCatInfo->createDate = gMagicBusyCreationDate;
  1230.         }
  1231.  
  1232.         err = FSCreateDirectoryUnicode(destDir, destName->length, destName->unicode,
  1233.                  kFSCatInfoSettableInfo,
  1234.                  sourceCatInfo,
  1235.                  &newDir, nil, nil);
  1236.     }
  1237.  
  1238.     // With the new APIs, folders can have forks as well as files.  Before
  1239.     // we start copying items in the folder, we
  1240.         
  1241.     if (err == noErr) {
  1242.         err = CopyItemsForks(source, &newDir, params);
  1243.     }
  1244.              
  1245.     // Iterate through the source code directory, copying each item to the newly
  1246.     // created directory.
  1247.     
  1248.     if (err == noErr) {
  1249.         err = FSOpenIterator(source, kFSIterateFlat, &iterator);
  1250.         if (err == noErr) {
  1251.         
  1252.             // Repeatedly call GetCatalogInfoBulk to get the contents
  1253.             // of the directory, and copy the contents.
  1254.             //
  1255.             // Note that we call GetCatalogInfoBulk with kFSCatInfoSettableInfo,
  1256.             // not kFSCatInfoGettableInfo, because there's no point getting
  1257.             // information that we can't set back when we're creating the copied item.
  1258.             
  1259.             done = false;
  1260.             do {
  1261.                 err = FSGetCatalogInfoBulk(iterator, kFolderItemsPerBulkCall,
  1262.                                             &foundItems, nil,
  1263.                                             kFSCatInfoSettableInfo,
  1264.                                             foundCatInfos, foundFSRefs, nil, foundNames);
  1265.                 if (err == errFSNoMoreItems) {
  1266.                     // We ran off the end of the directory.  Record that we're
  1267.                     // done, but set err to noErr so that we process any partial
  1268.                     // results.
  1269.                     done = true;            
  1270.                     err = noErr;
  1271.                 }
  1272.                 if (err == noErr) {
  1273.                     for (i = 0; i < foundItems; i++) {
  1274.                         if (foundCatInfos[i].nodeFlags & kFSNodeIsDirectoryMask) {
  1275.                             err = CopyFolder(&foundFSRefs[i], &foundCatInfos[i], &newDir, &foundNames[i], params);
  1276.                         } else {
  1277.                             err = CopyFile(&foundFSRefs[i], &foundCatInfos[i], &newDir, &foundNames[i], params);
  1278.                         }
  1279.                         if (err != noErr) {
  1280.                             break;
  1281.                         }
  1282.                     }
  1283.                 }
  1284.             } while (err == noErr && !done);
  1285.             
  1286.             junk = FSCloseIterator(iterator);
  1287.             MoreAssertQ(junk == noErr);
  1288.         }
  1289.     }
  1290.  
  1291.     // Reset the modification dates, except when copying to a drop folder
  1292.     // where this won't work.
  1293.  
  1294.     if (err == noErr && !params->copyingToDropFolder) {
  1295.         sourceCatInfo->createDate = originalCreateDate;
  1296.         err = FSSetCatalogInfo(&newDir,   kFSCatInfoCreateDate
  1297.                                         | kFSCatInfoAttrMod 
  1298.                                         | kFSCatInfoContentMod, sourceCatInfo);
  1299.     }
  1300.  
  1301.     // Clean up.
  1302.     
  1303.     if (foundCatInfos != nil) {
  1304.         MPFree(foundCatInfos);
  1305.     }    
  1306.     if (foundFSRefs != nil) {
  1307.         MPFree(foundFSRefs);
  1308.     }    
  1309.     if (foundNames != nil) {
  1310.         MPFree(foundNames);
  1311.     }    
  1312.     params->currentDepth -= 1;
  1313.     
  1314.     return err;
  1315. }
  1316.  
  1317. enum {
  1318.     kDestInsideSourceErr = -1234
  1319. };
  1320.  
  1321. static OSStatus CheckForDestInsideSource(const FSRef *source, const FSRef *destDir)
  1322.     // Determines whether the destination directory is equal to the source
  1323.     // item, or whether it's nested inside the source item.  Returns a
  1324.     // kDestInsideSourceErr if that's the case.  We do this to prevent
  1325.     // endless recursion while copying.
  1326.     //
  1327.     // Frame Size: 288
  1328. {
  1329.     OSStatus err;
  1330.     FSRef thisDir;
  1331.     Boolean done;
  1332.     FSCatalogInfo thisDirInfo;
  1333.     
  1334.     thisDir = *destDir;
  1335.     done = false;
  1336.     do {
  1337.         err = FSCompareFSRefs(source, &thisDir);
  1338.         if (err == noErr) {
  1339.             err = kDestInsideSourceErr;
  1340.         } else if (err == diffVolErr) {
  1341.             err = noErr;
  1342.             done = true;
  1343.         } else if (err == errFSRefsDifferent) {
  1344.         
  1345.             // This is somewhat tricky.  We can ask for the parent of thisDir
  1346.             // by setting the parentRef parameter to FSGetCatalogInfo but, if
  1347.             // thisDir is the volume's FSRef, this will give us back junk.
  1348.             // So we also ask for the parent's dir ID to be returned in the
  1349.             // FSCatalogInfo record, and then check that against the node
  1350.             // ID of the root's parent (ie 1).  If we match that, we've made
  1351.             // it to the top of the hierarchy without hitting source, so
  1352.             // we leave with no error.
  1353.             
  1354.             err = FSGetCatalogInfo(&thisDir, kFSCatInfoParentDirID, &thisDirInfo, nil, nil, &thisDir);
  1355.             if (err == noErr) {
  1356.                 if (thisDirInfo.parentDirID == fsRtParID) {
  1357.                     done = true;
  1358.                 }
  1359.             }
  1360.         }
  1361.     } while ( err == noErr && ! done );
  1362.     
  1363.     return err;
  1364. }
  1365.  
  1366. enum {
  1367.     kPrivilegesMask  = kioACUserNoSeeFolderMask | kioACUserNoSeeFilesMask | kioACUserNoMakeChangesMask,
  1368.     kDropFolderValue = kioACUserNoSeeFolderMask | kioACUserNoSeeFilesMask
  1369. };
  1370.  
  1371. static OSStatus CopyItemTopLevel(const FSRef *source, const FSRef *destDir,
  1372.                                     void *copyBuffer, ByteCount copyBufferSize)
  1373.     // This routine acts as the top level of the copy engine.  It exists
  1374.     // to a) present a nicer API than the various recursive routines, and
  1375.     // b) minimise the local variables in the recursive routines.
  1376.     //
  1377.     // Frame Size: 752
  1378. {
  1379.     OSStatus err;
  1380.     FSCatalogInfo     tmpCatInfo;
  1381.     HFSUniStr255      destName;
  1382.     CopyParams        params;
  1383.     FSVolumeRefNum    destVRefNum;
  1384.     
  1385.     params.copyBuffer     = copyBuffer;
  1386.     params.copyBufferSize = copyBufferSize;
  1387.     params.recursionLimit = kMaximumFolderDepth;
  1388.     params.currentDepth   = 0;
  1389.  
  1390.     // Get info on the destination to determine whether it is a drop folder.
  1391.     
  1392.     err = FSGetCatalogInfo(destDir, kFSCatInfoUserPrivs, &tmpCatInfo, nil, nil, nil);
  1393.     if (err == noErr) {
  1394.         params.copyingToDropFolder = ((tmpCatInfo.userPrivileges & kPrivilegesMask) == kDropFolderValue);
  1395.         
  1396.         // Get info on the source to decide whether to kick off a file or
  1397.         // directory copy.
  1398.  
  1399.         err = FSGetCatalogInfo(source, kFSCatInfoSettableInfo, &tmpCatInfo, &destName, nil, nil);
  1400.     }
  1401.     
  1402.     // Get info on the destination to determine whether it is on a network volume.
  1403.     
  1404.     if (err == noErr) {
  1405.         err = VolumeContainingFSRef(destDir, &destVRefNum);
  1406.     }
  1407.     if (err == noErr) {
  1408.         err = GetFileSystemInfo(destVRefNum, nil, ¶ms.copyingToLocalVolume, nil);
  1409.     }
  1410.     
  1411.     if (err == noErr) {
  1412.  
  1413.         // Clear the "inited" bit so that the Finder positions the icon for us.
  1414.         // We do this here because we only want to clear the "inited" bit for the
  1415.         // top level item.
  1416.     
  1417.         ((FInfo *)(tmpCatInfo.finderInfo))->fdFlags &= ~kHasBeenInited;
  1418.  
  1419.         if (tmpCatInfo.nodeFlags & kFSNodeIsDirectoryMask) {
  1420.             err = CheckForDestInsideSource(source, destDir);
  1421.             if (err == noErr) {
  1422.                 err = CopyFolder(source, &tmpCatInfo, destDir, &destName, ¶ms);
  1423.             }
  1424.         } else {
  1425.             err = CopyFile(source, &tmpCatInfo, destDir, &destName, ¶ms);
  1426.         }
  1427.     }
  1428.     return err;
  1429. }
  1430.  
  1431. /////////////////////////////////////////////////////////////////
  1432. #pragma mark ----- MP Stuff -----
  1433.  
  1434. static Boolean MPFileManagerAvailable(void)
  1435.     // Check to see whether we can use the File Manager from a
  1436.     // pre-emptive thread.  If gestaltMPCallableAPIsAttr isn't
  1437.     // available in your "Gestalt.h", update to the very latest.
  1438. {
  1439.     SInt32 response;
  1440.     return MPLibraryIsLoaded() 
  1441.             && Gestalt(gestaltMPCallableAPIsAttr, &response) == noErr 
  1442.             && (response & (1 << gestaltMPFileManager)) != 0;
  1443. }
  1444.  
  1445. // MP tasks take a single parameter when they start, but we need
  1446. // to pass a bunch of parameters to the task, so we put them
  1447. // in a structure.
  1448.  
  1449. struct CopyTaskParams {
  1450.     const FSRef *source;
  1451.     const FSRef *destDir;
  1452.     void *copyBuffer;
  1453.     ByteCount copyBufferSize;
  1454. };
  1455. typedef struct CopyTaskParams CopyTaskParams;
  1456.  
  1457. static OSStatus CopyFileTask(const CopyTaskParams *params)
  1458.     // This is the start routine of the MP task.  It simply
  1459.     // unpacks the parameters from the CopyTaskParams structure
  1460.     // and calls the copy engine.
  1461.     //
  1462.     // Frame Size: 64
  1463. {
  1464.     OSStatus err;
  1465.     
  1466.     err = CopyItemTopLevel(params->source, params->destDir, params->copyBuffer, params->copyBufferSize);
  1467.     return err;
  1468. }
  1469.  
  1470. static OSStatus CopyItemTopLevelMP(const FSRef *source, const FSRef *destDir,
  1471.                             void *copyBuffer, ByteCount copyBufferSize)
  1472.     // This routine is a direct analogue of CopyFileTopLevel, except it
  1473.     // executes the copy in an MP task.  The foreground process than
  1474.     // simply sits and waits for the task to complete, printing "."s
  1475.     // every now and again.  This is not a particularly useful application
  1476.     // of multi-processing (-: but it's still pretty cool.  For a start,
  1477.     // you can hold down the mouse button on a menu and the copy keeps
  1478.     // going.
  1479.     //
  1480.     // In a real application, this routine would probably be executed
  1481.     // by co-operative code that would drive the status window that
  1482.     // shows the user the status of the copy.  Pressing the Cancel
  1483.     // button on the status window would set a variable in the
  1484.     // CopyTaskParams that the MP task would periodically check.
  1485.     // [Calling MPTerminateTask would probably be a bad idea because
  1486.     // there'd be no way for the task to dispose of the memory that
  1487.     // it's allocated during the copy.]
  1488. {
  1489.     OSStatus err;
  1490.     OSStatus junk;
  1491.     MPQueueID terminationQueue;
  1492.     CopyTaskParams params;
  1493.     MPTaskID theCopyTask;
  1494.     Boolean done;
  1495.     OSStatus terminationErr;
  1496.     UInt32 timeLastPrinted;
  1497.     EventRecord event;
  1498.     
  1499.     terminationErr = noErr;                // Just to make for more obvious debugging.
  1500.  
  1501.     // Fill out the CopyTaskParams.
  1502.     
  1503.     params.source = source;
  1504.     params.destDir = destDir;
  1505.     params.copyBuffer = copyBuffer;
  1506.     params.copyBufferSize = copyBufferSize;
  1507.  
  1508.     // Create the termination queue and the task itself.
  1509.     
  1510.     err = MPCreateQueue(&terminationQueue);
  1511.     if (err == noErr) {
  1512.         err = MPCreateTask( (TaskProc) CopyFileTask, ¶ms,
  1513.                             kCopyFileTaskStackSize,
  1514.                             terminationQueue,
  1515.                             nil, nil,    // no termination parameters
  1516.                             0,            // no task options
  1517.                             &theCopyTask);
  1518.         
  1519.         // Now wait for the copying task to complete, printing "."s
  1520.         // while we wait.
  1521.         
  1522.         if (err == noErr) {
  1523.             done = false;
  1524.             timeLastPrinted = TickCount();
  1525.             do {
  1526.                 err = MPWaitOnQueue(terminationQueue,
  1527.                             nil, nil,    // not interested in these terminator parameters
  1528.                             (void **) &terminationErr,
  1529.                             kDurationImmediate);
  1530.                 if (err == noErr) {
  1531.                     done = true;
  1532.                     err = terminationErr;
  1533.                 } else if (err == kMPTimeoutErr) {
  1534.                     if (TickCount() > timeLastPrinted + 10) {
  1535.                         printf(".");
  1536.                         fflush(stdout);
  1537.                         timeLastPrinted = TickCount();
  1538.                     }
  1539.                     (void) WaitNextEvent(everyEvent, &event, 10, nil);
  1540.                     (void) SIOUXHandleOneEvent(&event);
  1541.                     err = noErr;
  1542.                 }
  1543.             } while (err == noErr && ! done );
  1544.         }
  1545.         
  1546.         junk = MPDeleteQueue(terminationQueue);
  1547.         MoreAssertQ(junk == noErr);
  1548.     }
  1549.  
  1550.     return err;
  1551. }
  1552.  
  1553. /////////////////////////////////////////////////////////////////
  1554. #pragma mark ----- Mainline -----
  1555.  
  1556. static OSStatus ExtractSingleItem(const NavReplyRecord *reply, FSRef *item)
  1557.     // This item extracts a single FSRef from a NavReplyRecord.
  1558.     // Nav makes it really easy to support 'odoc', but a real pain
  1559.     // to support other things.  *sigh*
  1560. {
  1561.     OSStatus err;
  1562.     SInt32 itemCount;
  1563.     FSSpec fss;
  1564.     AEKeyword junkKeyword;
  1565.     DescType junkType;
  1566.     Size junkSize;
  1567.     
  1568.     MoreAssertQ((AECountItems(&reply->selection, &itemCount) == noErr) && (itemCount == 1));
  1569.     
  1570.     err = AEGetNthPtr(&reply->selection, 1, typeFSS, &junkKeyword, &junkType, &fss, sizeof(fss), &junkSize);
  1571.     if (err == noErr) {
  1572.         MoreAssertQ(junkType == typeFSS);
  1573.         MoreAssertQ(junkSize == sizeof(FSSpec));
  1574.         
  1575.         // We call FSMakeFSSpec because sometimes Nav is braindead
  1576.         // and gives us an invalid FSSpec (where the name is empty).
  1577.         // While FSpMakeFSRef seems to handle that (and the file system
  1578.         // engineers assure me that that will keep working (at least
  1579.         // on traditional Mac OS) because of the potential for breaking
  1580.         // existing applications), I'm still wary of doing this so
  1581.         // I regularise the FSSpec by feeding it through FSMakeFSSpec.
  1582.         
  1583.         if (fss.name[0] == 0) {
  1584.             err = FSMakeFSSpec(fss.vRefNum, fss.parID, fss.name, &fss);
  1585.         }
  1586.         if (err == noErr) {
  1587.             err = FSpMakeFSRef(&fss, item);
  1588.         }
  1589.     }
  1590.     return err;
  1591. }
  1592.  
  1593. static OSStatus ChooseSourceAndDest(FSRef *source, FSRef *dest)
  1594.     // Use Navigation Services (because I'm a good Apple employee)
  1595.     // to choose a source file and a destination folder,
  1596.     // and return them as FSRefs.
  1597. {
  1598.     OSStatus err;
  1599.     OSStatus junk;
  1600.     NavDialogOptions options;
  1601.     NavReplyRecord reply;
  1602.     
  1603.     err = noErr;
  1604.     if (! NavServicesAvailable() ) {
  1605.         printf("Navigation Services is required.\n");
  1606.         err = -1;
  1607.     }
  1608.     if (err == noErr) {
  1609.         err = NavGetDefaultDialogOptions(&options);
  1610.     }
  1611.     
  1612.     // Get the source item.
  1613.     
  1614.     if (err == noErr) {
  1615.         options.dialogOptionFlags &= ~kNavAllowMultipleFiles;
  1616.         err = NavChooseObject(nil, &reply, &options, nil, nil, nil);
  1617.     }
  1618.     if (err == noErr) {
  1619.         err = ExtractSingleItem(&reply, source);
  1620.         
  1621.         junk = NavDisposeReply(&reply);
  1622.         MoreAssertQ(junk == noErr);
  1623.     }
  1624.     
  1625.     // Get the dest folder.
  1626.     
  1627.     if (err == noErr) {
  1628.         err = NavChooseFolder(nil, &reply, &options, nil, nil, nil);
  1629.     }
  1630.     if (err == noErr) {
  1631.         err = ExtractSingleItem(&reply, dest);
  1632.         
  1633.         junk = NavDisposeReply(&reply);
  1634.         MoreAssertQ(junk == noErr);
  1635.     }
  1636.     
  1637.     // Would be nice if I made the destination name unique at this point.
  1638.     // Too hard for the moment.
  1639.     
  1640.     return err;
  1641. }
  1642.  
  1643. void main(void)
  1644.     // The main line.  Calls the copy engine to copy an item
  1645.     // named "Source" in the same directory as the application
  1646.     // to a folder named "Dest" in the same directory.
  1647. {
  1648.     OSStatus err;
  1649.     FSRef source;
  1650.     FSVolumeRefNum sourceVol;
  1651.     FSRef dest;
  1652.     FSVolumeRefNum destVol;
  1653.     Ptr copyBuffer;
  1654.     Boolean runPreemptive;
  1655.     ByteCount bufferSize;
  1656.     
  1657.     printf("Hello Cruel World!\n");
  1658.     printf("MPFileCopy.c\n");
  1659.     
  1660.     // gState is just for debugging.
  1661.     
  1662.     printf("&gState = $%08x\n", &gState);
  1663.     
  1664.     // We flush stdout so that the SIOUX window comes up, which initialises
  1665.     // the toolbox for us, which we're going to need as soon as we
  1666.     // call ChooseSourceAndDest, which calls Navigation Services.
  1667.     
  1668.     fflush(stdout);
  1669.  
  1670.     #if !TARGET_API_MAC_CARBON
  1671.         gMyCompletionUPP = NewIOCompletionProc(MyCompletion);
  1672.         MoreAssert(gMyCompletionUPP != nil);
  1673.     #endif
  1674.  
  1675.     copyBuffer = nil;
  1676.     
  1677.     // Get source and dest objects using Nav.
  1678.     
  1679.     err = ChooseSourceAndDest(&source, &dest);
  1680.  
  1681.     // Decide whether we can run using MP tasks or not.
  1682.     
  1683.     if (err == noErr) {
  1684.         err = VolumeContainingFSRef(&source, &sourceVol);
  1685.     }
  1686.     if (err == noErr) {
  1687.         err = VolumeContainingFSRef(&dest, &destVol);
  1688.     }
  1689.     runPreemptive = ! kNeverUseMPTasks && MPFileManagerAvailable();
  1690.     if (err == noErr && runPreemptive) {
  1691.         err = IsVolumeTrulyAsync(sourceVol, &runPreemptive);
  1692.         if (err == noErr && runPreemptive && destVol != sourceVol) {
  1693.             err = IsVolumeTrulyAsync(destVol, &runPreemptive);
  1694.         }
  1695.     }
  1696.     
  1697.     // Allocate the copy buffer.  We do this late because we
  1698.     // need to know the source and destination volumes to
  1699.     // judge the copy buffer size.
  1700.     
  1701.     if (err == noErr) {
  1702.         bufferSize = BufferSizeForThisVolume(sourceVol);
  1703.         if (destVol != sourceVol) {
  1704.             ByteCount tmp;
  1705.             
  1706.             tmp = BufferSizeForThisVolume(destVol);
  1707.             if (tmp < bufferSize) {
  1708.                 bufferSize = tmp;
  1709.             }
  1710.         }
  1711.         copyBuffer = NewPtr(bufferSize);
  1712.         err = MemError();
  1713.     }
  1714.     
  1715.     // Under Mac OS 9.0, File Sharing's patches cause FSCreateFileUnicode to go
  1716.     // astray.  We determine whether it's running now and then test the gFileSharingIsRunning
  1717.     // flag later to see whether we need to use the workaround.
  1718.     
  1719.     if (err == noErr) {
  1720.         err = IsFileSharingRunning(&gFileSharingIsRunning);
  1721.     }
  1722.     
  1723.     // The copy engine is going to set each item's creation date
  1724.     // to kMagicBusyCreationDate while it's copying the item.
  1725.     // But kMagicBusyCreationDate is an old-style 32-bit date/time,
  1726.     // while the HFS Plus APIs use the new 64-bit date/time.  So
  1727.     // we have to call a happy UTC utilities routine to convert from
  1728.     // the local time kMagicBusyCreationDate to a UTCDateTime
  1729.     // gMagicBusyCreationDate, which the File Manager will store
  1730.     // on disk and which the Finder we read back using the old
  1731.     // APIs, whereupon the File Manager will convert it back
  1732.     // to local time (and hopefully get the kMagicBusyCreationDate
  1733.     // back!).
  1734.     
  1735.     if (err == noErr) {
  1736.         gMagicBusyCreationDate.highSeconds = 0;
  1737.         gMagicBusyCreationDate.fraction = 0;
  1738.         err = ConvertLocalTimeToUTC(kMagicBusyCreationDate, &gMagicBusyCreationDate.lowSeconds);
  1739.     }
  1740.     
  1741.     // Get the constant names for the resource and data fork, which
  1742.     // we're going to need inside the copy engine.
  1743.     
  1744.     if (err == noErr) {
  1745.         err = FSGetDataForkName(&gDataForkName);
  1746.     }
  1747.     if (err == noErr) {
  1748.         err = FSGetResourceForkName(&gRsrcForkName);
  1749.     }
  1750.     
  1751.     // Now call copy engine.
  1752.     
  1753.     if (err == noErr) {
  1754.         if ( runPreemptive ) {
  1755.             printf("Copying using pre-emptive thread.\n");
  1756.             fflush(stdout);
  1757.             err = CopyItemTopLevelMP(&source, &dest, copyBuffer, GetPtrSize(copyBuffer));
  1758.         } else {
  1759.             printf("Copying using co-operative main thread.\n");
  1760.             fflush(stdout);
  1761.             err = CopyItemTopLevel(&source, &dest, copyBuffer, GetPtrSize(copyBuffer));
  1762.         }
  1763.     }
  1764.     
  1765.     // Clean up.
  1766.     
  1767.     if (copyBuffer != nil) {
  1768.         DisposePtr(copyBuffer);
  1769.         MoreAssertQ(MemError() == noErr);
  1770.     }
  1771.     
  1772.     if (err == noErr) {
  1773.         printf("\nSuccess.\n");
  1774.     } else {
  1775.         printf("\nFailed with error %ld.\n", err);
  1776.     }
  1777.     printf("Done.  Press command-Q to Quit.\n");
  1778. }
  1779.